<template>
  <div ref="button" class="slider-button-wrapper" :class="{ 'hover': hovering, 'dragging': dragging }"
       :style="wrapperStyle" tabindex="0" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave"
       @mousedown="onButtonDown" @touchstart="onButtonDown" @focus="handleMouseEnter" @blur="handleMouseLeave"
       @keydown.left="onLeftKeyDown" @keydown.right="onRightKeyDown" @keydown.down.prevent="onLeftKeyDown"
       @keydown.up.prevent="onRightKeyDown">
    <slot name="sliderContent" />
  </div>
</template>

<script>

export default {
  name: 'ElSliderButton',

  components: {

  },

  props: {
    value: {
      type: Number,
      default: 0
    },
    vertical: {
      type: Boolean,
      default: false
    },
    tooltipClass: {
      type: String,
      default: ''
    }
  },

  data() {
    return {
      hovering: false,
      dragging: false,
      isClick: false,
      startX: 0,
      currentX: 0,
      startY: 0,
      currentY: 0,
      startPosition: 0,
      newPosition: null,
      oldValue: this.value
    }
  },

  computed: {
    disabled() {
      return this.$parent.sliderDisabled
    },

    max() {
      return this.$parent.max
    },

    min() {
      return this.$parent.min
    },

    step() {
      return this.$parent.step
    },

    showTooltip() {
      return this.$parent.showTooltip
    },

    precision() {
      return this.$parent.precision
    },

    currentPosition() {
      return `${(this.value - this.min) / (this.max - this.min) * 100}%`
    },

    enableFormat() {
      return this.$parent.formatTooltip instanceof Function
    },

    formatValue() {
      return this.enableFormat && this.$parent.formatTooltip(this.value) || this.value
    },

    wrapperStyle() {
      return this.vertical ? { bottom: this.currentPosition } : { left: this.currentPosition }
    }
  },

  watch: {
    dragging(val) {
      this.$parent.dragging = val
    }
  },

  methods: {
    displayTooltip() {
      this.$refs.tooltip && (this.$refs.tooltip.showPopper = true)
    },

    hideTooltip() {
      this.$refs.tooltip && (this.$refs.tooltip.showPopper = false)
    },

    handleMouseEnter() {
      this.hovering = true
      this.displayTooltip()
    },

    handleMouseLeave() {
      this.hovering = false
      this.hideTooltip()
    },

    onButtonDown(event) {
      if (this.disabled) return
      // event.preventDefault()
      this.onDragStart(event)
      window.addEventListener('mousemove', this.onDragging)
      window.addEventListener('touchmove', this.onDragging)
      window.addEventListener('mouseup', this.onDragEnd)
      window.addEventListener('touchend', this.onDragEnd)
      window.addEventListener('contextmenu', this.onDragEnd)
    },
    onLeftKeyDown() {
      if (this.disabled) return
      this.newPosition = parseFloat(this.currentPosition) - this.step / (this.max - this.min) * 100
      this.setPosition(this.newPosition)
      this.$parent.emitChange()
    },
    onRightKeyDown() {
      if (this.disabled) return
      this.newPosition = parseFloat(this.currentPosition) + this.step / (this.max - this.min) * 100
      this.setPosition(this.newPosition)
      this.$parent.emitChange()
    },
    onDragStart(event) {
      this.dragging = true
      this.isClick = true
      if (event.type === 'touchstart') {
        event.clientY = event.touches[0].clientY
        event.clientX = event.touches[0].clientX
      }
      if (this.vertical) {
        this.startY = event.clientY
      } else {
        this.startX = event.clientX
      }
      this.startPosition = parseFloat(this.currentPosition)
      this.newPosition = this.startPosition
    },

    onDragging(event) {
      if (this.dragging) {
        this.$parent.dragStatus = true
        this.isClick = false
        this.displayTooltip()
        this.$parent.resetSize()
        let diff = 0
        if (event.type === 'touchmove') {
          event.clientY = event.touches[0].clientY
          event.clientX = event.touches[0].clientX
        }
        if (this.vertical) {
          this.currentY = event.clientY
          diff = (this.startY - this.currentY) / this.$parent.sliderSize * 100
        } else {
          this.currentX = event.clientX
          diff = (this.currentX - this.startX) / this.$parent.sliderSize * 100
        }
        this.newPosition = this.startPosition + diff
        this.setPosition(this.newPosition)
      }
    },

    onDragEnd() {
      if (this.dragging) {
        /*
         * 防止在 mouseup 后立即触发 click，导致滑块有几率产生一小段位移
         * 不使用 preventDefault 是因为 mouseup 和 click 没有注册在同一个 DOM 上
         */
        setTimeout(() => {
          this.$parent.dragStatus = false
          this.dragging = false
          this.hideTooltip()
          if (!this.isClick) {
            this.setPosition(this.newPosition)
            this.$parent.emitChange()
          }
        }, 0)
        window.removeEventListener('mousemove', this.onDragging)
        window.removeEventListener('touchmove', this.onDragging)
        window.removeEventListener('mouseup', this.onDragEnd)
        window.removeEventListener('touchend', this.onDragEnd)
        window.removeEventListener('contextmenu', this.onDragEnd)
      }
    },

    setPosition(newPosition) {
      if (newPosition === null || isNaN(newPosition)) return
      if (newPosition < 0) {
        newPosition = 0
      } else if (newPosition > 100) {
        newPosition = 100
      }
      const lengthPerStep = 100 / ((this.max - this.min) / this.step)
      const steps = Math.round(newPosition / lengthPerStep)
      let value = steps * lengthPerStep * (this.max - this.min) * 0.01 + this.min
      value = parseFloat(value.toFixed(this.precision))
      this.$emit('input', value)
      this.$nextTick(() => {
        this.displayTooltip()
        this.$refs.tooltip && this.$refs.tooltip.updatePopper()
      })
      if (!this.dragging && this.value !== this.oldValue) {
        this.oldValue = this.value
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.slider-button-wrapper {
  line-height: normal;
  position: absolute;
  z-index: 1001;
  bottom: 12px;
  left: 50%;
  width: max-content;
  user-select: none;
  transform: translateX(-50%);
  text-align: center;
  outline: none;
  background-color: transparent;

  &:hover,
  &.hover {
    cursor: grab;
  }

  &.dragging {
    cursor: grabbing;
  }
}
</style>
